﻿
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;


namespace Boilen.Primitives.Implementers {

    /// <summary>
    /// Implements an accessor, such as a property or event.
    /// </summary>
    /// <typeparam name="T">The type of the accessor.</typeparam>
    public abstract class AccessorImplementer<T> : Implementer<T> {

        private readonly List<Guard> guards_ = new List<Guard>( );
        private readonly List<AttributeMember> attributes_ = new List<AttributeMember>( );


        /// <summary>
        /// Gets or sets a value indicating the accessibility of a member.
        /// </summary>
        public Accessibility Accessibility { get; set; }

        /// <summary>
        /// Gets or sets a value indicating how a member is inherited.
        /// </summary>
        public Inheritance Inheritance { get; set; }

        /// <summary>
        /// Gets or sets a value indicating the design category of the member.
        /// </summary>
        public string Category { get; set; }

        /// <summary>
        /// Gets the collection of argument guards for the accessor.
        /// </summary>
        public IEnumerable<Guard> Guards { get { return this.guards_.Where( g => !g.IsValueGuard ); } }

        /// <summary>
        /// Gets the collection of value guards for the accessor.
        /// </summary>
        public IEnumerable<Guard> AccessorGuards { get { return this.guards_.Where( g => g.IsValueGuard ); } }

        /// <summary>
        /// Gets the collection of attributes for the accessor.
        /// </summary>
        public IEnumerable<AttributeMember> Attributes {
            get {
                if( this.Category != null ) {
                    var typeRepository = this.Parent.TypeRepository;
                    string typeName = typeRepository.GetTypeName( typeof( CategoryAttribute ) );
                    string argValue = typeRepository.GetValueString( this.Category, false );
                    yield return AttributeMember.Custom( typeName, argValue );
                }

                foreach( AttributeMember attribute in this.attributes_ )
                    yield return attribute;
            }
        }


        /// <summary>
        /// Gets the name of the accessor's backing field.
        /// </summary>
        public virtual string FieldName { get { return this.FieldNamePrefix + this.AccessorName; } }

        /// <summary>
        /// Gets the name of the accessor's backing field type.
        /// </summary>
        public virtual string FieldTypeName { get { return this.TypeName; } }

        /// <summary>
        /// Gets the modifiers on the accessor's backing field.
        /// </summary>
        public virtual string FieldModifiers { get { return "private"; } }


        /// <summary>
        /// Gets the public name of the accessor.
        /// </summary>
        public string AccessorName { get { return this.MemberName; } }

        /// <summary>
        /// Gets the modifiers on the accessor.
        /// </summary>
        public string AccessorModifiers { get { return this.GetAccessibilityValue( ) + this.GetInheritanceValue( ); } }


        /// <summary>
        /// Gets the prefix for the accessor's backing field.
        /// </summary>
        protected abstract string FieldNamePrefix { get; }

        /// <summary>
        /// Gets the backing field member for the accessor, if any.
        /// </summary>
        protected virtual IEnumerable<FieldMember> Fields {
            get { yield return new FieldMember( this.FieldName, this.FieldTypeName ) { Modifiers = this.FieldModifiers }; }
        }

        /// <summary>
        /// Gets the initialization members for the accessor.
        /// </summary>
        protected abstract IEnumerable<InitializationMember> Initializers { get; }

        /// <summary>
        /// Gets the accessor member for the accessor.
        /// </summary>
        protected abstract AccessorMember Accessor { get; }

        /// <inheritdoc/>
        protected override bool EnsureDescription {
            get { return base.EnsureDescription && this.Accessibility < Accessibility.Internal; }
        }


        /// <inheritdoc/>
        protected AccessorImplementer( PartialType parent, string name, string description )
            : base( parent, name, description ) { }


        /// <summary>
        /// Ensures the accessor satisfies the requirements defined by the specified condition.
        /// </summary>
        public AccessorImplementer<T> AccessorSatisfies( string description, Action<Guard> configure, string condition, string format, params string[] args ) {
            return this.AddGuard( Guard.AccessorSatisfies( this.CreateDoc( ), "this", description, condition, format, args ), configure );
        }
        public AccessorImplementer<T> AccessorSatisfies( string description, string condition, string format, params string[] args ) { return this.AccessorSatisfies( description, null, condition, format, args ); }

        /// <summary>
        /// Adds a new guard to the accessor.
        /// </summary>
        public AccessorImplementer<T> AddGuard( Guard guard, Action<Guard> configure ) {
            Ensure.NotNull( guard );

            if( configure != null )
                configure( guard );

            this.guards_.Add( guard );
            return this;
        }


        /// <summary>
        /// Assigns the values used for the <see cref="Accessibility"/> property.
        /// </summary>
        public AccessorImplementer<T> SetAccessibility( Accessibility accessibility ) {
            this.Accessibility = accessibility;
            return this;
        }

        /// <summary>
        /// Assigns the values used for the <see cref="Inheritance"/> property.
        /// </summary>
        public AccessorImplementer<T> SetInheritance( Inheritance inheritance ) {
            this.Inheritance = inheritance;
            return this;
        }

        /// <summary>
        /// Assigns the values used for the <see cref="Category"/> property.
        /// </summary>
        public AccessorImplementer<T> SetCategory( string category ) {
            this.Category = category;
            return this;
        }

        /// <summary>
        /// Adds a custom attribute to the accessor.
        /// </summary>
        public AccessorImplementer<T> AddAttribute( Type type, CompilationSymbol condition, string[] positionalArguments, params string[] namedArguments ) {
            Ensure.Satisfies( typeof( Attribute ).IsAssignableFrom( type ), "{0} is not an Attribute type.", type );

            string typeName = this.Parent.TypeRepository.GetTypeName( type );
            var attribute = AttributeMember.Custom( typeName, positionalArguments );
            attribute.Condition = condition;

            for( int i = 0; i < namedArguments.Length; ++i ) {
                string namedArgument = namedArguments[i];
                string[] nameValue = namedArgument.Split( new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries );
                string name = nameValue[0];
                string value = nameValue[1];

                attribute.NamedArguments[name] = value;
            }

            this.attributes_.Add( attribute );
            return this;
        }
        public AccessorImplementer<T> AddAttribute( Type type, string[] positionalArguments, params string[] namedArguments ) { return this.AddAttribute( type, CompilationSymbol.None, positionalArguments, namedArguments ); }

        /// <summary>
        /// Adds a custom attribute to the accessor.
        /// </summary>
        public AccessorImplementer<T> AddAttribute<TArg>( Type type, CompilationSymbol condition, TArg arg ) {
            Ensure.Satisfies( typeof( Attribute ).IsAssignableFrom( type ), "{0} is not an Attribute type.", type );

            var typeRepository = this.Parent.TypeRepository;
            string typeName = typeRepository.GetTypeName( type );
            string argValue = typeRepository.GetValueString( arg, false );
            AttributeMember attribute = AttributeMember.Custom( typeName, argValue );
            attribute.Condition = condition;
            this.attributes_.Add( attribute );

            return this;
        }
        public AccessorImplementer<T> AddAttribute<TArg>( Type type, TArg arg ) { return this.AddAttribute<TArg>( type, CompilationSymbol.None, arg ); }

        /// <summary>
        /// Adds a custom suppress message attribute to the accessor.
        /// </summary>
        public AccessorImplementer<T> AddSuppressionAttribute( string category, string id, string justification ) {
            this.attributes_.Add( AttributeMember.SuppressMessage( category, id, justification ) );
            return this;
        }


        /// <inheritdoc/>
        protected override IEnumerable<Member> GetMembers( ) {
            foreach( var field in this.Fields )
                yield return field;

            foreach( var initializer in this.Initializers )
                yield return initializer;

            var accessor = this.Accessor;
            Ensure.NotNull( accessor );
            accessor.AddAttributes( this.Attributes );
            yield return accessor;
        }

        /// <summary>
        /// Gets the member name of the specified action for this implementer.
        /// </summary>
        protected string GetActionName( string action ) {
            return this.AccessorName + action;
        }

        /// <summary>
        /// Creates a new <see cref="BlockMember"/> for an accessor.
        /// </summary>
        protected BlockMember CreateAccessorBlock( string name, string content ) {
            return InitializeAccessorBlock( new BlockMember( name, content ) );
        }

        /// <summary>
        /// Creates a new <see cref="BlockMember"/> for an accessor.
        /// </summary>
        protected BlockMember CreateAccessorBlock( string name, Action<ICodeWriter> content ) {
            return InitializeAccessorBlock( new BlockMember( name, content ) );
        }


        private BlockMember InitializeAccessorBlock( BlockMember block ) {
            block.WriteName = true;
            block.AddGuards( this.AccessorGuards );
            return block;
        }

        private string GetAccessibilityValue( ) {
            return GetAccessibilityValue( this.Accessibility );
        }

        protected static string GetAccessibilityValue( Accessibility accessibility ) {
            return Util.Lowercase( accessibility.ToString( ) );
        }

        private string GetInheritanceValue( ) {
            switch( this.Inheritance ) {
                case Inheritance.Virtual:
                    return " virtual";
                case Inheritance.Override:
                    return " override";
                case Inheritance.New:
                    return " new";
                case Inheritance.None:
                default:
                    return "";
            }
        }

    }

}
